#include <ostream>
#include "stdafx.h"
#include "../common/WindbgExt.h"
#include "../common/ScriptExtInterface.h"
#include <string>
#include "perl_io_layer.h"
#include <perliol.h>
#include <io.h>
#include <fcntl.h>
#include <share.h>

extern WindbgExt *dbgengine;
int PrintErr(const char* msg, int ret);

// Unix layer instance data
struct PerlIOWindbg: public _PerlIO
{
	int fd;                     /* UNIX like file descriptor */
	int oflags;                 /* open/fcntl flags */
}; 

typedef SSize_t (*PerlIOWriteType) (pTHX_ PerlIO *f, const void *vbuf, Size_t count);
typedef SSize_t (*PerlIOReadType) (pTHX_ PerlIO *f, void *vbuf, Size_t count);
typedef PerlIO* (*PerlIOOpenType) (pTHX_ PerlIO_funcs *tab,
	PerlIO_list_t *layers, IV n,
	const char *mode,
	int fd, int imode, int perm,
	PerlIO *old, int narg, SV **args);
typedef IV (*PerlIOFlushType)(pTHX_ PerlIO *f);


PerlIO_funcs *unixLayer = NULL;

PerlIOOpenType orgUnixOpen = NULL;
std::string buf;

PerlIOWriteType orgCrlfWrite = NULL;
PerlIOReadType orgCrlfRead = NULL;
PerlIOFlushType orgCrlfFlush = NULL;


PerlIO* PerlIOUnix_open_patch(pTHX_ PerlIO_funcs *self, PerlIO_list_t *layers,
	IV n, const char *mode, int fd, int imode,
	int perm, PerlIO *f, int narg, SV **args)
{
	PerlIO *fnew = orgUnixOpen(aTHX_ self, layers, n, mode, fd, imode, perm, f, narg, args);
	if (fnew)
	{
		if (narg > 0)
		{
			STRLEN len;
			const char *path = SvPV_const(*args, len);
			if (path && (strcmp(path, "con") == 0))
			{
				PerlIOWindbg *self = PerlIOSelf(fnew, PerlIOWindbg);
				self->flags |= 0x40000000;
			}
		}
	}
	return fnew;
}


SSize_t PerlIOCrlf_write_patch(pTHX_ PerlIO *f, const void *vbuf, Size_t count)
{
	if (!(*f)->next || ((*f)->next->tab != unixLayer))
		return orgCrlfWrite(aTHX_ f, vbuf, count);

	PerlIOWindbg *self = PerlIOSelf(&((*f)->next), PerlIOWindbg);
	if ((self->fd == 1) || (self->flags & 0x40000000) || (self->flags & PERLIO_F_NOTREG))
	{
		buf.append(reinterpret_cast<const char*>(vbuf), count);
		if (buf.find('\n') != std::string::npos)
			FlushIo();
		return count;
	}
	else if (self->fd == 2)
	{
		if (dbgengine)
		{
			std::string tmp(reinterpret_cast<const char*>(vbuf), count);
			dbgengine->Err(tmp.c_str());
		}
		return count;
	}
	return orgCrlfWrite(aTHX_ f, vbuf, count);
}

SSize_t PerlIOCrlf_read_patch(pTHX_ PerlIO *f, void *vbuf, Size_t count)
{
	if (!(*f)->next || ((*f)->next->tab != unixLayer))
		return orgCrlfWrite(aTHX_ f, vbuf, count);

	PerlIOWindbg *self = PerlIOSelf(&((*f)->next), PerlIOWindbg);
	if ((self->fd == 0) || (self->flags & 0x40000000) || (self->flags & PERLIO_F_NOTREG))
	{
		if (dbgengine)
			return dbgengine->Input(reinterpret_cast<char*>(vbuf), count);
	}
	return orgCrlfRead(aTHX_ f, vbuf, count);
}

IV PerlIOCrlf_flush_patch(pTHX_ PerlIO * f)
{
	PERL_UNUSED_ARG(f);
	if ((*f)->next && ((*f)->next->tab == unixLayer))
		FlushIo();
	if (orgCrlfFlush)
		return orgCrlfFlush(aTHX_ f);
	return 0;
}


int PatchPerlIoLayer(PerlInterpreter* engine)
{
	// get functions from the unix layer
	unixLayer = PerlIO_find_layer(engine, "unix", 0, FALSE);
	if (!unixLayer)
		return PrintErr("Cannot get 'unix' IO layer functions", 1);
	if (unixLayer->Open != PerlIOUnix_open_patch)
	{
		orgUnixOpen = unixLayer->Open;
		unixLayer->Open = PerlIOUnix_open_patch;
	}

	PerlIO_funcs *crlfLayer = PerlIO_find_layer(engine, "crlf", 0, FALSE);
	if (!crlfLayer)
		return PrintErr("Cannot get 'crlf' IO layer functions", 1);
	if (crlfLayer->Write != PerlIOCrlf_write_patch)
	{
		orgCrlfWrite = crlfLayer->Write;
		orgCrlfRead = crlfLayer->Read;
		orgCrlfFlush = crlfLayer->Flush;
		crlfLayer->Write = PerlIOCrlf_write_patch;
		crlfLayer->Read = PerlIOCrlf_read_patch;
		crlfLayer->Flush = PerlIOCrlf_flush_patch;
	}

	return 0;
}

void FlushIo()
{
	if (dbgengine && !buf.empty())
		dbgengine->Out(buf.c_str());
	buf.clear();
}